<?php

// php D:/xampp/htdocs/pps/test.php

require_once(__DIR__ . '/pps.php');

// AOP：进入、离开方法时触发，下面的 $rtn 参数在进入时总是null，可以在这里修改它和 $agms；
$mthd_in  = pps::fun('进入方法后触发该AOP', 
	//        返回值 方法名  $self  其他参数
	function (&$rtn, $mthd, $self, &$agms) 
	{ 
		// 展示通过AOP修改参数
		if ('getName' === $mthd)
		{
			$agms[0] = 'AOP -------- ' . $agms[0];
		}

		echo $self->name . ' ' . end($self->__callstk) . '::' . $mthd . '() in: ' . PHP_EOL;// . implode(',', $agms) . PHP_EOL; 
	}
);
$mthd_out = pps::fun('方法返回前触发该AOP', 
	function (&$rtn, $mthd, $self, &$agms) 
	{ 
		// 展示通过AOP修改返回值
		if ('getName' === $mthd)
		{
			$rtn .= ' ======== AOP';
		}

		echo $self->name . ' ' . end($self->__callstk) . '::' . $mthd . '() out: ' . PHP_EOL;// . $rtn . ';' . implode(',', $agms) . PHP_EOL; 
	}
);

// 显示AOP内联文档
echo pps::help($mthd_in) . PHP_EOL;
echo pps::help($mthd_out) . PHP_EOL;
echo PHP_EOL;

// 定义类
pps::class(
	'tA',  // 类名
	null, // 基类名，null或空串表示不继承
	[
		// 初始化函数，类似于PHP的__construct()
		'__init' => [
			[ // 类的帮助信息，可以是字符串或行数组，这里用行数组：
				'(int age)', // 约定第一行给出初始化（构造）函数签名，不必写函数名，也不必写self
				'类A，展示PPS里如何定义类', // 约定第二行，概述本类作用
				'age: 年龄',  // 约定第三行起，介绍初始化（构造）函数的参数
			],
			function ($self, $age = 0) // 初始化函数，类似于PHP的__construct
			{
				echo 'tA::__init()' . PHP_EOL;
				$self->name = '??';
				$self->age = $age;
			},
		],

		// 定义方法的简洁形式，只需提供一个lambda函数作为方法本体，
		// 约定所有方法第一个参数为$self，对应PHP内建的$this
		'getAge' => function ($self) { return $self->age; },

		// 定义方法的高级形式，提供一个数组：
		'getName' => [
			// “内联文档”，如果有，只能位于[0]，形式是字符串或行数组，推荐行数组（将用PHP_EOL连成字符串）
			[
				'str (str title)', // 约定第一行给出函数签名，不必写函数名，也不必写self
				'获取属性 name', // 约定第二行给出功能描述和返回值
				'title: 头衔', // 从第三行起，介绍每个参数
			],
			// 直接注册AOP，只是演示，实际中很少这么用
			$mthd_in,
			// 方法本体
			function ($self, $title = 'Title') { return $title . '~' . $self->name; },
			// 直接注册AOP，只是演示
			$mthd_out, 
		],
		// 虚函数，表示允许覆盖祖先类同名方法，这里的其实可以不写，但下面那个必须写
		'virtual setAge' => function ($self, $age) 
		{
			$self->age = $age; 
		},
	]
);

// 继承上面的类
pps::class(
	'tB', 
	'tA', // 继承
	[
		// 初始化函数，类似于PHP的__construct()
		'__init' => [
			'类B的help', // 一个字符串，不用行数组
			function ($self, $age = 0, $gender = 'M')
			{
				echo 'tB::__init()' . PHP_EOL;
				pps::parent(null, $self, $age); // 调用基类构造函数，传null/'__init'
				$self->gender = $gender;
			},
		],

		'getGender' => function ($self) { return $self->gender; },
		// 这个“virtual ”（注意空格）如果不写，会异常
		'virtual setAge' => function ($self, $age) 
		{
			pps::parent('setAge', $self, $age); // 调用基类版本
		},
	]
);

// 示范AOP用法，实际中一般这么用，这样就不用修改上面的类代码
pps::aop_enter('tA', 'setAge', $mthd_in);
pps::aop_leave('tA', 'setAge', $mthd_out);

pps::aop_enter('tB', 'setAge', $mthd_in);
pps::aop_leave('tB', 'setAge', $mthd_out);


// 开始测试：

echo 'tA 作用：' . PHP_EOL;
echo pps::help('tA') . PHP_EOL; // 展示类的帮助信息
echo PHP_EOL;

echo 'tA::getName() 用法：' . PHP_EOL;
echo pps::help('tA', 'getName') . PHP_EOL; // 展示方法的帮助信息
echo PHP_EOL;

$a = pps::tA(24); // 直接调用构造函数，就能创建实例
echo PHP_EOL;

$b = pps::$class['tB'](24, 'M'); // 通过字符串创建派生类的实例，可当基类实例用
echo PHP_EOL;

echo $b->getName('Title'); echo PHP_EOL; // Title~??
$b->name = 'Mad';
echo $b->getName('Title'); echo PHP_EOL; // Title~Mad
echo PHP_EOL;

echo $b->age; echo PHP_EOL; // 24
$b->setAge(32);
++$b->age;
echo $b->age; echo PHP_EOL; // 33
echo PHP_EOL;

echo $b->gender; echo PHP_EOL; // M
$b->gender = 'F';
echo $b->gender; echo PHP_EOL; // F
echo PHP_EOL;

// 展示重复定义类 —— 相当于“热更新”
// 首先利用类版本控制，避免重复定义相同的类
if (!pps::class_ver('tC', 'ver1')) // 这句意思是：仅当tC类存在且版本不是"ver1"，才定义它
{
	pps::class('', // 类名不必重复，也不必指定版本，使用上一步的两个参数
		'tB', // 从tB继承
		[
			'who' => function ($self) { return 'I am tC1'; },
		]
	);
}

$c = pps::tC();
echo $c->who() . PHP_EOL; // I am tC1

// 由于tC类存在且版本正是"ver1"，这个if比较不会通过，算是一种性能优化
if (!pps::class_ver('tC', 'ver1')) // 
{
	echo '不会执行到这里！！！' . PHP_EOL;
	pps::class('', 
		'tB', 
		[
			'who' => function ($self) { return 'I am tC1'; },
		]
	);
}
echo $c->who() . PHP_EOL; // I am tC1

// tC类第二版，覆盖前面的定义
// 注意：进程执行到这里并没有中断、重启，但代码却变了 —— 热更新！
if (!pps::class_ver('tC', 'ver2'))
{
	pps::class(null, // null和""一个效果
		'tB', 
		[
			'who' => function ($self) { return 'I am tC2'; },
		]
	);
}
echo $c->who() . PHP_EOL; // I am tC2 —— 注意，变量$c立即“变身”成新tC的实例！
echo PHP_EOL;
// 这种立即变身可能不是好事，如果更改了类的定义，最好重建原类的所有实例！
// 更符合实际的做法是，不要“跨 include 复用实例”，意思是，
// 每次 include PPS 代码，都新建 PPS 类实例，并使用这些新建的实例，
// 不要试图复用之前的实例，因为方法的代码可能变了，而实例身上的属性尚未相应改变！
// 这算是热更新的一点代价吧，毕竟无论何种技术，都有优点与缺点。

// 类的静态变量&方法，直接定义在类上！因为PPS里，类是“一等对象（first-class object）”
// 遗憾的是，静态方法目前还不支持内联文档和AOP，如果非要内联文档，可以利用 ppsfun。
$tC = pps::$class['tC']; // 这个就是类了，和字符串、数字……同属“一等公民”
$tC->svar = '类的静态变量可以这么定义';
echo $tC->svar . PHP_EOL;
$tC->smthd = function () { return '类的静态方法可以这么定义'; };
echo $tC->smthd() . PHP_EOL;
$tC->smthd_doc = pps::fun( 
	[
		'str tC::smthd_doc()',
		'静态方法的文档……',
	],
	function () { return '带有内联文档的静态方法'; }
);
echo pps::help($tC->smthd_doc) . PHP_EOL;
echo $tC->smthd_doc() . PHP_EOL;
echo PHP_EOL;

// 演示列出所有的类，和指定类所有方法、静态变量、静态方法、祖先类……
echo implode(PHP_EOL, pps::list_class()) . PHP_EOL;
echo PHP_EOL;
echo implode(PHP_EOL, pps::list_method('tB')) . PHP_EOL;
echo PHP_EOL;
echo implode(PHP_EOL, pps::list_svar('tC')) . PHP_EOL;
echo PHP_EOL;
echo implode(PHP_EOL, pps::list_smethod('tC')) . PHP_EOL;
echo PHP_EOL;
echo implode(PHP_EOL, pps::list_ancestor('tC')) . PHP_EOL;
echo PHP_EOL;
echo (pps::instanceof($c, 'tC') ? '$c 是 tC 类的实例' : '不是？！') . PHP_EOL;
echo PHP_EOL;

// 展示 PPS 又一大特性 —— 双分派！
// 虚函数是单分派，PHP本身不支持双分派（事实上很少有语言内建支持双分派），
// 我们用两种几何形状间的碰撞检测来示范用法，注意碰撞关系是一种对称关系，频道以“^”开头
pps::class('atShape'); // “形状”，“at”表示“抽象类型”
pps::class('tQuad', 'atShape'); // 四边形 “是” 形状
pps::class('tEllipse', 'atShape'); // 椭圆 “是” 形状
pps::class('tPara', 'tQuad'); // 平行四边形 “是” 四边形
pps::class('tRect', 'tQuad'); // 矩形 “是” 四边形
pps::class('tCircle', 'tEllipse'); // 圆 “是” 椭圆

$rect = pps::tRect();
$circle = pps::tCircle();
$para = pps::tPara();
$agms = array('a1', 'a2'); // 示范还可以传参数
$agms_a2 = $agms[1];
$agms[1] = &$agms_a2; // 参数甚至可以是引用

pps::dpcttf('^collide', 'tQuad', 'tEllipse', // 计算四边形与椭圆碰撞
	function ($one, $two, $a1, &$a2)
	{
		echo 'tQuad vs tEllipse : ' . $a1 . ', ' . $a2 . PHP_EOL;
	}
);

pps::dpcttf('^collide', 'tRect', 'tCircle', // 计算矩形与圆碰撞
	function ($one, $two, $a1, &$a2)
	{
		echo 'tRect vs tCircle : ' . $a1 . ', ' . $a2 . PHP_EOL;
	}
);

pps::dispatch('^collide', $rect, $circle, $agms); // tRect vs tCircle : a1, a2
pps::dispatch('^collide', $circle, $rect, $agms); // tRect vs tCircle : a1, a2
pps::dispatch('^collide', $circle, $para, $agms); // tQuad vs tEllipse : a1, a2

pps::dpcttf('^collide', 'tPara', 'tCircle', // 上一步用的是两个基类间的碰撞函数，为派生类定制一个
	function ($one, $two, $a1, &$a2)		// 计算平行四边形与圆
	{
		$a2 .= '~collide!!'; // 修改a2，实际改变的是agms_a2！
		echo 'tPara vs tCircle : ' . $a1 . ', ' . $a2 . PHP_EOL;
	}
);
pps::dispatch('^collide', $circle, $para, $agms); // tPara vs tCircle : a1, a2~collide!!
echo 'agms_a2 = ' . $agms_a2 . PHP_EOL; // a2~collide!!
echo PHP_EOL;

?>